<?php
/**
 * Mssql
 *
 * @package php.db
 */

/**
 * Mssql 연결 클래스 
 * 
 * @package php.db
 */
class Mssql extends DBClient {

	/** 
	 * 생성자
	 */
	public function __construct($option = '', $type = 'product') {
		parent::__construct($option, $type);
	}

	/**
	 * 기본 속성 이외의 다른속성 실행할 때 
	 * 
	 * 
	 * @see lib/php/db/DBClient#runOption()
	 */
	public function runOption() { 
		// remote 설정 
		if ($this->remote) { 
			$this->remote();
		}
	}	

	/**
	 * database 연결 생성
	 * 
	 * @return resource 연결 resource
	 */
	protected function _connect() { 
		$connection = mssql_connect(sprintf("%s,%s", $this->host, $this->port), $this->id, $this->pass, $this->newlink);

		$selected	= mssql_select_db($this->db, $connection);

		return ($connection && $selected) ? $connection : false;

	}

	/** 
	 * DB 선택하기 
	 */
	public function selectDB($db_name) { 
		return mssql_select_db($db_name, $this->getConnection());
	}

	/**
	 * 실행된 row 수 
	 *
	 * @return int 
	 */
	public function affectedRows() { 
		return mssql_rows_affected($this->getConnection());
	}

	/**
	 * 다음 배치 레코드 가지고 오기 
	 *
	 * @param resource $result select 결과 resource
	 * @return bool
	 * @see Mssql::query()
	 */
	public function batch($result) { 
		return mssql_fetch_batch($result);
	}

	/**
	 * 트랜잭션 시작 
	 * 
	 * @return bool 
	 * @see Mssql::commit(), Mssql::rollback();
	 */
	public function begin() { 
		return $this->query("BEGIN TRAN");
	}

	/**
	 * 연결 닫기 
	 */
	public function close() {
		if ($this->getConnection()) { 
			mssql_close($this->getConnection());
		}
	}

	/** 
	 * commit 
	 *
	 * mssql은 자동으로 commit을 하기 때문에 자동commit 을 하지 않을려면 반드시 begin() 메소드를 먼저 실행해야한다.
	 * 
	 * @return bool
	 * @see Mssql::begin(), Mssql::rollback();
	 */
	public function commit() { 
		return $this->query("COMMIT TRAN");
	}

	/**
	 * 페이징된 DBData 객체 생성 
	 *
	 * @param resource $result select 쿼리의 resource 
	 * @param int $page 현재 페이지 
	 * @param int $pagesize 한 페이지당 리스트 개수 
	 * @param string $baseClass 초기화될 DBData 상속 클래스 이름 
	 * @return DBData
	 */
	public function createPageData($result, $page, $pagesize) {
		// 기본적으로 Data 클래스를 사용하고 
		// Data 클래스를 상속한 다른 Data 클래스를 사용하면 된다. 
		$data = new DBData();
		$data->setConnector($this);

		//필드 리스트 
		$data->setFields($this->getFieldList($result));
		
		if (!$result) 
			return $data;

		while($d = $this->fetch($result)) 
			$data->add($d);

		return $data;
	}

	/**
	 * 에러 메세지 리턴
	 * 
	 * @return string 에러 메세지 
	 */
	public function error() {
		return "";
	}




	/**
	 * 실행된 select 쿼리에 대해서 fetch 를 통해 한 행(row)을 리턴한다. 
	 *
	 * 리턴되는 값은 array('필드' => 'ㅁㅁㅁ', ...); 형태를 가진다. 
	 * 
	 * @param resource $result select 결과로 나온 resource
	 * @return array row 데이타 
	 */
	public function fetch($result) {
		return mssql_fetch_assoc($result);
	}

	/** 
	 * 메모리 해제 
	 * 
	 * @return bool
	 */
	public function free($result) { 
		return mssql_free_result($result);
	}

	/**
	 * 쿼리의 결과로 나온 필드의 개수를 리턴한다.
	 *
	 * @param resource $result select 결과로 나온 resource
	 * @return int 필드개수 
	 */
	public function getFieldCount($result) {
		return mssql_num_fields($result);
	}

	/**
	 * 정해진 위치의 필드 이름을 얻어온다. 
	 *
	 * @param resource $result select 결과로 나온 resource
	 * @param int $i 필드 위치, 처음은 0 
	 * @return string 필드이름 
	 */
	public function getFieldName($result, $i) {
		return mssql_field_name($result, $i);
	}

	/**
	 * 정해진 위치의 필드 타입을 얻어온다. 
	 *
	 * @param resource $result select 결과로 나온 resource
	 * @param int $i 필드 위치, 처음은 0 
	 * @return string 필드타입
	 */
	public function getFieldType($result, $i) {
		return mssql_field_type($result, $i);
	}

	/**
	 * select 쿼리에 대해서 페이징된 DBData를 리턴한다. 
	 *
	 * @param string $query select 쿼리
	 * @param int $page 현재 페이지  
	 * @param int $count 페이지당 리스트 개수 
	 * @param string $baseClass  DBData 를 상속한 클래스 , 기본값 DBData  
	 * @return DBData
	 */
	public function getPageData($query, $page = 1, $count = 10) {
		$total = $page * $count;

		$query = "select top {$total} * from ($query) TEMP_SELECT ";

		// 구현..
		$this->query($query, $total);

		// 위치 이동 
		$this->seek(($page-1)*$count);

		$data = $this->createData($this->getResult());

		return $data;

	}

	/**
	 * 필드 타입(자료형)별 php용 전용 상수 얻어오기 
	 *
	 * @param string $type 타입문자열 
	 * @return int|string PHP상수
	 */
	public function getTypeConstant($type)
	{
		$type = strtolower($type);

		switch ($type) {
			case 'char':
				return SQLCHAR;
			case 'smalldatetime' :
			case 'datetime' :
			case 'varchar': 
			case 'nvarchar':
			case 'binary':
			case 'image':
			case 'varbinary':
			case 'sql_variant':
				return SQLVARCHAR;
			case 'tinyint': 
			case 'smallint':
			case 'int': 
			case 'bigint':
			case 'timestamp':
				return SQLINT4;
			CASE 'text' : 
			CASE 'ntext':
				return SQLTEXT;
			case 'bit' : 
				return SQLBIT;
			case 'float' :
			case 'decimal' :
			case 'money' : 
			case 'real':
			case 'numeric':
			case 'smallmoney':
			case 'money':
				return SQLFLT8;
			default: 
				return SQLVARCHAR;
		}
	}

	/**
	 * 필드 타입(자료형)별 DB에 입력될 수 있는 문자열 얻어오기 
	 *
	 * @param string $type 타입문자열 
	 * @param mixed $value 입력값
	 * @param boolean $is_null 널을 체크할 것인지 여부 , true 널체크, false 널 체크 안함 
	 * @return string 변환된 문자열
	 */
	public function getTypeString($type, $value, $is_null = false) { 
		$type = strtoupper($type);
		$temp = '';
		switch ($type) {
			case 'binary':
			case 'char': 
			case 'datetime' :
			case 'image':
			case 'smalldatetime' :
			case 'sql_variant':
			case 'text': 
			case 'varbinary':
			case 'varchar': 
				$temp = sprintf("'%s'", $this->escape($value));
				break;
			case 'nchar':
			case 'ntext':
			case 'nvarchar':
				$temp = sprintf("N'%s'", $this->escape($value));
				break;
			case 'bigint':
			case 'bit' : 
			case 'int': 
			case 'smallint':
			case 'timestamp':
			case 'tinyint': 
				$temp = intval($value);
				break;
			case 'decimal' :
			case 'float' :
			case 'money' : 
			case 'real':
				$temp = floatval($value);
				break;
			default: 
				$temp = $value;
				break;
		}

		return ($is_null && is_null($value)) ? 'NULL' : $temp;
	}



	/**
	 * 실행한 쿼리가 2개 이상의 select 로 되어 있다면 다음 result set을 반환한다. 
	 *
	 * <code>
	 * $resource = $mssql->query("select 1; select 2");
	 * 
	 * $second_result = $mssql->nextResult($result);
	 *
	 * echo $mssql->fetch($second_result);   // array([2] => 2)
	 *
	 * </code>
	 * 
	 * @param resource $result select 결과 resource
	 * @return resource 두번째 select 결과 resource
	 */
	public function nextResult($result) {
		return mssql_next_result($result);
	}


	/** 
	 * 일반 쿼리 실행 
	 * 
	 * @param string $sql 실행될 query 
	 * @param int $batch_size 실행되어서 한번에 메모리에 적재될 레코드 수 
	 * @return resource 실행 resource
	 */
	public function query($sql, $batch_size = -1) {

		$this->addSql($sql);

		$args = array($sql, $this->getConnection());

		if ($batch_size > 0) { 
			$args[] = $batch_size;
		} 

		$this->setResult(call_user_func_array('mssql_query', $args));

		return $this->getResult();
	}	

	/**
	 * 링크드 서버 쿼리를 실행할때 미리 실행해주어야 하는 메소드
	 *
	 * @return bool 
	 */
	public function remote()
	{
		return $this->query("SET ANSI_NULLS ON ; SET ANSI_WARNINGS ON;");
	}

	/**
	 * 지정된 row에서 필드 값을 바로 얻어옴 
	 *
	 * @param resource $result select 결과 resource
	 * @param int $row row번호, 시작은 0  
	 * @param int|string $field 필드위치(시작은 0) 또는 필드이름, 또는 table.필드이름 형태
	 * @return mixed
	 */
	public function result($result, $row, $field) 
	{
		return mssql_result($result, $row, $field);
	}

	/** 
	 * rollback
	 *
	 * @return bool 
	 */
	public function rollback() { 
		return $this->query("ROLLBACK TRAN");
	}

	/**
	 * 커서 이동 
	 * 
	 * @param int $count 이동시킬 상대위치, 사직행은 0 
	 * @return bool
	 */
	public function seek($count) { 
		return mssql_data_seek($this->getResult(), $count);
	}

	/** 
	 * 프로시저 초기화 
	 * 
	 * @param string $sp_name 프로시저이름 
	 * @return resource 초기화 resource
	 * @deprecated 
	 */
	public function prepare($sp_name) {
		$this->setStatement(mssql_init($sp_name, $this->getConnection()));

		return $this->getStatement();
	}

	/** 
	 * bind 메소드 
	 *
	 * @param string $name 바인딩 이름 
	 * @param string $type 바인딩될 타입, 데이타베이스 마다 틀림 
	 * @param string $output output 형태, in, out, inout, return 
	 * @param int $length 데이타길이, -1은 길이제한 없음
	 * @param mixed $value 저장될 데이타 또는 output 변수에 저장될 데이타
	 * @return bool 
	 */
	public function bind($name, $type = '', $output = 'in', $length = -1, &$value = null){

		$output = strtolower($output);		

		// 파라미터 이름 
		$param	= '@'.$name;

		if ($output == 'return') { 
			$param = 'RETVAL';
		}

		// output 설정 
		$isOutput = ($output == 'in') ? false : true;

		// 널 설정 
		$isNull = true;

		return mssql_bind($this->getStatement(), $param, $value, $this->getTypeConstant($type), $isOutput, $isNull, $length);
	}


	/**
	 * 프로시저 실행 
	 *
	 * 프로시저 결과 resource로 select 쿼리가 마지막으로 적용이 되었다면 select 쿼리의 resource를 반환한다. 
	 * 
	 * @return resource|bool 프로시저 결과 리소스
	 * @deprecated 
	 */
	public function execute() {
		return mssql_execute($this->getStatement());
	}

	/**
	 * 프로시저 메모리 해제 
	 * 
	 * @param resource $stmt mssql_init() 함수로 나온 결과 resource 
	 * @return bool  
	 * @see Mssql::init()
	 */
	public function freeStatement($stmt) {
		return mssql_free_statement($stmt);
	}
	
	/**
	 * 프로시저를 실행할 객체를 얻는다. 
	 * 
	 * @see lib/php/db/DBClient#createProc($name)
	 * @return MssqlProc 
	 */
	public function createProc($str, $option = 'proc') { 
		return new MssqlProc($this, $str, $option);
	}	
}

?>